package com.xingmang.admin.aspect;

import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.support.spring.PropertyPreFilters;
import com.xingmang.common.annotation.OperateLog;
import com.xingmang.common.utils.TokenUtils;
import com.xingmang.common.vo.admin.LoginUserVO;
import com.xingmang.db.entity.SysLog;
import com.xingmang.db.service.SysLogDBService;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.LocalVariableTableParameterNameDiscoverer;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;

/**
 * @auther: hansom
 * @Version: 1.0
 * @create 2023/4/21 13:49
 */
//@Component
@Aspect
@Slf4j
public class LogAop {
    @Resource
    SysLogDBService sysLogDBService;

    private long start = 0L;
    private long end = 0L;


    //公共切入点
    @Pointcut("@annotation(com.xingmang.common.annotation.OperateLog)")
    public void pointcut() {
    }

    @Pointcut("execution(public * com.xingmang..*.controller..*.*(..))")
    public void controllerPointcut() {
    }

    @Before("controllerPointcut()")
    public void doBefore(JoinPoint joinPoint) throws Throwable {

        Signature signature = joinPoint.getSignature();
        String name = signature.getName();

        log.info("------------- 开始 -------------");
        log.info("类名方法: {}.{}", signature.getDeclaringTypeName(), name);

        Object[] args = joinPoint.getArgs();
        Object[] arguments = new Object[args.length];
        for (int i = 0; i < args.length; i++) {
            if (args[i] instanceof ServletRequest
                    || args[i] instanceof ServletResponse
                    || args[i] instanceof MultipartFile) {
                continue;
            }
            arguments[i] = args[i];
        }

        String[] excludeProperties = {"password", "file"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
        excludeFilter.addExcludes(excludeProperties);
        log.info("请求参数: {}", JSONObject.toJSONString(arguments, excludeFilter));
    }

    @Around("controllerPointcut()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        long startTime = System.currentTimeMillis();
        Object result = proceedingJoinPoint.proceed();

        String[] excludeProperties = {"password", "file"};
        PropertyPreFilters filters = new PropertyPreFilters();
        PropertyPreFilters.MySimplePropertyPreFilter excludeFilter = filters.addFilter();
        excludeFilter.addExcludes(excludeProperties);
        log.info("返回结果: {}", JSONObject.toJSONString(result, excludeFilter));
        log.info("------------- 结束 耗时：{} ms -------------", System.currentTimeMillis() - startTime);
        return result;
    }

    /**
     * 后置通知
     * @param joinPoint
     * @param operateLog
     */
    @After("pointcut() && @annotation(operateLog)")
    public void doAfterInService(JoinPoint joinPoint, OperateLog operateLog) {
        //如果通知类型为Before不执行以下语句
        if(operateLog.adviseType().equals("before") ){
            return;
        }
        LoginUserVO userVo = null;
        try {
            userVo = TokenUtils.getLoginUser();
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        //调用本类添加日志方法
        this.addOperatorLog(joinPoint,operateLog,userVo);

    }
    /**
     * 前置通知（专为登出设计，因为用后置通知先退出，就没有用户信息了）
     * @param joinPoint
     * @param operateLog
     */
    @Before("pointcut() && @annotation(operateLog)")
    public void doBeforeInService(JoinPoint joinPoint,  OperateLog operateLog) {
        start = System.currentTimeMillis();
        //如果通知类型为After不执行以下语句
        if(operateLog.adviseType().equals("after") ){
            return;
        }
        LoginUserVO userVo = null;
        try {
            userVo = TokenUtils.getLoginUser();
        } catch (Exception e) {
            e.printStackTrace();
            return;
        }
        //调用本类添加日志方法
        this.addOperatorLog(joinPoint,operateLog,userVo);
    }

    /**
     * @param operateLog 操作信息
     * @param userVo 用户信息
     */
    public void addOperatorLog(JoinPoint joinPoint,OperateLog operateLog, LoginUserVO userVo) {
        //从获取RequestAttributes中获取HttpServletRequest的信息
        HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()).getRequest();
        MethodSignature signature = (MethodSignature)joinPoint.getSignature();
        String name = signature.getName();
        Method method = signature.getMethod();
        //登录日志
        SysLog log = new SysLog();

        Object[] args = joinPoint.getArgs();
        LocalVariableTableParameterNameDiscoverer u = new LocalVariableTableParameterNameDiscoverer();
        String[] paramNames = u.getParameterNames(method);
        if (args != null && paramNames != null) {
            StringBuilder paramsBuilder = new StringBuilder();
            for (int i = 0; i < args.length; i++) {
                String paramName = paramNames[i];
                Object arg = args[i];
                //考虑到参数过长的情况，故而截取
                paramsBuilder.append(paramName).append(": ");
                String paramValue = String.valueOf(arg);
                if (paramValue.length() > 50) {
                    paramsBuilder.append(paramValue.substring(0, 50));
                } else {
                    paramsBuilder.append(paramValue);
                }
                paramsBuilder.append("...");
                if (i != args.length - 1) {
                    paramsBuilder.append("  ,");
                }
            }
            // 设置请求的方法参数名称
            log.setParameter(paramsBuilder.toString());
        }
        log.setMethod(signature.getDeclaringTypeName()+"."+name);
        log.setModule(operateLog.module());
        log.setName(operateLog.name());
        log.setUserId(userVo.getId());
        log.setUsername(userVo.getLoginname());
        log.setType(operateLog.type());
        end = System.currentTimeMillis();
        log.setResponseTime("" + (end - start));
        //登录、登出时这两个字段不会用到，占用字段较长，不能存入日志
//        userVo.setHerRoles(null);
//        userVo.setRoles(null);
        log.setIp(request.getRemoteHost());
        sysLogDBService.save(log);
    }
}
